package com.limiterframework.core;

import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.ZParams;

import java.util.List;
import java.util.UUID;

/**
 * 进行应用限流核心逻辑
 * 依据java.util.concurrent相关工具包的信号量相关代码的设计思想，采用redis重写。实现给应用限流
 */
public class DoFlowLimiter {
    /**
     *排序set,用来计算排名
     */
    private static final String LIMITER = "LIMITER_";
    /**
     * 计数器自增
     */
    private static final String LIMITER_COUNT = "LIMITER_COUNT";
    /**
     * 排序set,存储key的时间，用来过期
     */
    private static final String LIMITER_MONITOR = "LIMITER_MONITOR_";

    /**
     * 此方法用于获取信号量，获取信号量则代表允许进行本次调用
     * @param jedis redis连接
     * @param point 方法
     * @param limit 连接个数上限
     * @param timeout 超级时间
     * @return 返回信号量
     */
    public static String enAbleToAccess(
            Jedis jedis, String point, int limit, long timeout) {

        if(jedis==null){
            throw new NullPointerException("jedis is null");
        }
        if(point==null){
            throw new NullPointerException("methodname must not be null");
        }
        if(limit<=0){
            throw new RuntimeException("limit must >0  ");
        }
        if(timeout<=0){
            throw  new RuntimeException("timeout must >=0");
        }

        String identifier = UUID.randomUUID().toString();
        long now = System.currentTimeMillis();
        Transaction transaction = jedis.multi();

        //删除信号量
        transaction.zremrangeByScore((LIMITER_MONITOR + point).getBytes(), "-inf".getBytes(), String.valueOf(now - timeout).getBytes());
        ZParams params = new ZParams();
        params.weightsByDouble(1.0, 0.0);
        transaction.zinterstore(LIMITER + point, params, LIMITER + point, LIMITER_MONITOR+point);

        //计数器自增
        transaction.incr(LIMITER_COUNT);
        List<Object> results = transaction.exec();
        long counter = (Long) results.get(results.size() - 1);

        transaction = jedis.multi();
        transaction.zadd(LIMITER_MONITOR + point, now, identifier);
        transaction.zadd(LIMITER + point, counter, identifier);
        transaction.zrank(LIMITER + point, identifier);
        results = transaction.exec();
        //获取排名，判断请求是否取得了信号量
        long rank = (Long) results.get(results.size() - 1);
        if (rank < limit) {
            return identifier;
        } else {
            //没有获取到信号量，清理之前放入redis 中垃圾数据
            transaction = jedis.multi();
            transaction.zrem(LIMITER_MONITOR + point, identifier);
            transaction.zrem(LIMITER + point, identifier);
            transaction.exec();
        }
        return null;
    }
}
